﻿namespace Infra.NHibernateRepository
{
    using System.Collections.Generic;
    using NHibernate;
    using NHibernate.Criterion;
    using NHibernateUnitOfWork;
    using SourceCodeRepository.Common;
    using SourceCodeRepository.Common.Interfaces.Model;
    using SourceCodeRepository.Common.Interfaces.Repository;

    /// <summary>
    /// Repository Generic Implementation to be used with NHibernate
    /// </summary>
    /// <typeparam name="T">The concrete repository</typeparam>
    public class Repository<T> : IRepository<T> where T : IDomainObject
    {
        /// <summary>
        /// Gets the entities.
        /// </summary>
        /// <value>The entities.</value>
        protected DetachedCriteria Entities
        {
            get
            {
                return DetachedCriteria.For<T>();
            }
        }

        /// <summary>
        /// Gets the current session.
        /// </summary>
        /// <value>The get session.</value>
        protected ISession GetSession
        {
            get
            {
                return UnitOfWork.CurrentSession;
            }
        }
        
        /// <summary>
        /// Saves the specified item.
        /// </summary>
        /// <param name="item">The item to save.</param>
        public void Save(T item)
        {
            var tx = UnitOfWork.Current.BeginTransaction();

            try
            {
                this.GetSession.Save(item);
                tx.Commit();
            }
            catch
            {
                tx.Rollback();
            }
            finally
            {
                tx.Dispose();
            }

        }

        /// <summary>
        /// Updates the specified item.
        /// </summary>
        /// <param name="item">The item to update.</param>
        public void Update(T item)
        {
            var tx = UnitOfWork.Current.BeginTransaction();

            try
            {
                this.GetSession.Update(item);
                tx.Commit();
            }
            catch
            {
                tx.Rollback();
            }
            finally
            {
                tx.Dispose();
            }

        }

        /// <summary>
        /// Gets all entities.
        /// </summary>
        /// <param name="pageNumber">The page number.</param>
        /// <returns>A list of entities</returns>
        public IList<T> GetAll(int? pageNumber)
        {
            var tx = UnitOfWork.Current.BeginTransaction();

            try
            {
                int firstResults = ((pageNumber ?? 1) * CommonConstants.MaxPages) - CommonConstants.MaxPages;
                ICriteria crit = this.GetSession.CreateCriteria(typeof(T));
                crit.SetMaxResults(CommonConstants.MaxPages);
                crit.SetFirstResult(firstResults);

                tx.Commit();

                return crit.List<T>();
            }
            catch
            {
                tx.Rollback();
                return new List<T>();
            }
            finally
            {
                tx.Dispose();
            }
        }

        /// <summary>
        /// Get a single object by Id
        /// </summary>
        /// <param name="id">The Id</param>
        /// <returns>An object</returns>
        public T GetById(int id)
        {
            var tx = UnitOfWork.Current.BeginTransaction();

            try
            {
                ICriteria crit = this.GetSession.CreateCriteria(typeof(T));
                crit.Add(Expression.Eq("Id", id));

                tx.Commit();

                return crit.UniqueResult<T>();
            }
            catch
            {
                tx.Rollback();
                return default(T);
            }

        }

        /// <summary>
        /// Gets all entities.
        /// </summary>
        /// <returns></returns>
        public IList<T> GetAllEntities()
        {
            var tx = UnitOfWork.Current.BeginTransaction();
            try
            {
                var entitiesList = this.Entities.GetExecutableCriteria(this.GetSession).List<T>();
                tx.Commit();

                return entitiesList;
            }
            catch
            {
                tx.Rollback();
                return new List<T>();
            }
            finally
            {
                tx.Dispose();
            }
        }
    }
}